import json
import logging
import mimetypes
from datetime import timedelta
from io import BytesIO

from django.conf import settings
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.http import (
    FileResponse,
    HttpResponse,
    HttpResponseBadRequest,
    JsonResponse,
)
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from pypdf import PdfWriter
from reportlab.lib.units import mm

from eventyay.base.i18n import language
from eventyay.base.models import CachedFile, InvoiceAddress, OrderPosition
from eventyay.base.pdf import get_images, get_variables
from eventyay.base.settings import PERSON_NAME_SCHEMES
from eventyay.control.permissions import EventPermissionRequiredMixin
from eventyay.helpers.database import rolledback_transaction
from eventyay.presale.style import get_fonts

logger = logging.getLogger(__name__)


class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
    template_name = 'pretixcontrol/pdf/index.html'
    permission = 'can_change_settings'
    accepted_formats = ('application/pdf',)
    maxfilesize = 1024 * 1024 * 10
    minfilesize = 10
    title = None

    def get(self, request, *args, **kwargs):
        resp = super().get(request, *args, **kwargs)
        resp._csp_ignore = True
        return resp

    def process_upload(self):
        f = self.request.FILES.get('background')
        error = False
        if f.size > self.maxfilesize:
            error = _('The uploaded PDF file is too large.')
        if f.size < self.minfilesize:
            error = _('The uploaded PDF file is too small.')
        if mimetypes.guess_type(f.name)[0] not in self.accepted_formats:
            error = _('Please only upload PDF files.')
        # if there was an error, add error message to response_data and return
        if error:
            return error, None
        return None, f

    def _get_preview_position(self):
        item = self.request.event.items.create(
            name=_('Sample product'),
            default_price=42.23,
            description=_('Sample product description'),
        )
        item2 = self.request.event.items.create(name=_('Sample workshop'), default_price=23.40)

        from pretix.base.models import Order

        order = self.request.event.orders.create(
            status=Order.STATUS_PENDING,
            datetime=now(),
            email='sample@eventyay.com',
            locale=self.request.event.settings.locale,
            expires=now(),
            code='PREVIEW1234',
            total=119,
        )

        scheme = PERSON_NAME_SCHEMES[self.request.event.settings.name_scheme]
        sample = {k: str(v) for k, v in scheme['sample'].items()}
        p = order.positions.create(item=item, attendee_name_parts=sample, price=item.default_price)
        order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p)
        order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p)

        InvoiceAddress.objects.create(order=order, name_parts=sample, company=_('Sample company'))
        return p

    def generate(self, p: OrderPosition, override_layout=None, override_background=None):
        raise NotImplementedError()

    def get_layout_settings_key(self):
        raise NotImplementedError()

    def get_background_settings_key(self):
        raise NotImplementedError()

    def get_default_background(self):
        raise NotImplementedError()

    def get_current_background(self):
        return (
            self.request.event.settings.get(self.get_background_settings_key()).url
            if self.request.event.settings.get(self.get_background_settings_key())
            else self.get_default_background()
        )

    def get_current_layout(self):
        return self.request.event.settings.get(self.get_layout_settings_key(), as_type=list)

    def save_layout(self):
        self.request.event.settings.set(self.get_layout_settings_key(), self.request.POST.get('data'))

    def save_background(self, f: CachedFile):
        fexisting = self.request.event.settings.get(self.get_background_settings_key(), as_type=File)
        if fexisting:
            try:
                default_storage.delete(fexisting.name)
            except OSError:  # pragma: no cover
                logger.error('Deleting file %s failed.' % fexisting.name)

        # Create new file
        nonce = get_random_string(length=8)
        fname = 'pub/%s-%s/%s/%s.%s.%s' % (
            'event',
            'settings',
            self.request.event.pk,
            self.get_layout_settings_key(),
            nonce,
            'pdf',
        )
        newname = default_storage.save(fname, f.file)
        self.request.event.settings.set(self.get_background_settings_key(), 'file://' + newname)

    def post(self, request, *args, **kwargs):
        if 'emptybackground' in request.POST:
            p = PdfWriter()
            try:
                p.add_blank_page(
                    width=float(request.POST.get('width')) * mm,
                    height=float(request.POST.get('height')) * mm,
                )
            except ValueError:
                return JsonResponse({'status': 'error', 'error': 'Invalid height/width given.'})
            buffer = BytesIO()
            p.write(buffer)
            buffer.seek(0)
            c = CachedFile(web_download=True)
            c.expires = now() + timedelta(days=7)
            c.date = now()
            c.filename = 'background_preview.pdf'
            c.type = 'application/pdf'
            c.save()
            c.file.save('empty.pdf', ContentFile(buffer.read()))
            c.refresh_from_db()
            return JsonResponse(
                {
                    'status': 'ok',
                    'id': c.id,
                    'url': reverse(
                        'control:pdf.background',
                        kwargs={
                            'event': request.event.slug,
                            'organizer': request.organizer.slug,
                            'filename': str(c.id),
                        },
                    ),
                }
            )

        if 'background' in request.FILES:
            error, fileobj = self.process_upload()
            if error:
                return JsonResponse({'status': 'error', 'error': error})
            c = CachedFile(web_download=True)
            c.expires = now() + timedelta(days=7)
            c.date = now()
            c.filename = 'background_preview.pdf'
            c.type = 'application/pdf'
            c.file = fileobj
            c.save()
            c.refresh_from_db()
            return JsonResponse(
                {
                    'status': 'ok',
                    'id': c.id,
                    'url': reverse(
                        'control:pdf.background',
                        kwargs={
                            'event': request.event.slug,
                            'organizer': request.organizer.slug,
                            'filename': str(c.id),
                        },
                    ),
                }
            )

        cf = None
        if request.POST.get('background', '').strip():
            try:
                cf = CachedFile.objects.get(id=request.POST.get('background'))
            except CachedFile.DoesNotExist:
                pass

        if 'preview' in request.POST:
            with (
                rolledback_transaction(),
                language(request.event.settings.locale, request.event.settings.region),
            ):
                p = self._get_preview_position()
                fname, mimet, data = self.generate(
                    p,
                    override_layout=(
                        json.loads(self.request.POST.get('data')) if self.request.POST.get('data') else None
                    ),
                    override_background=cf.file if cf else None,
                )

            resp = HttpResponse(data, content_type=mimet)
            ftype = fname.split('.')[-1]
            resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
            return resp
        elif 'data' in request.POST:
            if cf:
                self.save_background(cf)
            self.save_layout()
            return JsonResponse({'status': 'ok'})
        return HttpResponseBadRequest()

    def get_variables(self):
        return get_variables(self.request.event)

    def get_images(self):
        return get_images(self.request.event)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['fonts'] = get_fonts()
        ctx['pdf'] = self.get_current_background()
        ctx['variables'] = self.get_variables()
        ctx['images'] = self.get_images()
        ctx['layout'] = json.dumps(self.get_current_layout())
        ctx['title'] = self.title
        ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
        return ctx


class FontsCSSView(TemplateView):
    content_type = 'text/css'
    template_name = 'pretixcontrol/pdf/webfonts.css'

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['fonts'] = get_fonts()
        return ctx


class PdfView(TemplateView):
    def get(self, request, *args, **kwargs):
        cf = get_object_or_404(CachedFile, id=kwargs.get('filename'), filename='background_preview.pdf')
        resp = FileResponse(cf.file, content_type='application/pdf')
        resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
        return resp
